iT邦幫忙

2024 iThome 鐵人賽

DAY 27
0
Software Development

我的SpringBoot絕學:7+2個專案,從新手變專家系列 第 27

Day27 前端專案:Vue.js(1)完成註冊、登入與登出

  • 分享至 

  • xImage
  •  

我們現在要來做購物車專案的前端,使用框架是Vue.js。


打開VSCode,開啟終端機,輸入

bun create vue@latest

以下是建立專案時的設定

✔ Project name: … shopping_cart_frontend
✔ Add TypeScript? … No
✔ Add JSX Support? … No
✔ Add Vue Router for Single Page Application development? … Yes
✔ Add Pinia for state management? … Yes
✔ Add Vitest for Unit testing? … No
✔ Add an End-to-End Testing Solution? … No
✔ Add ESLint for code quality? … Yes
✔ Add Prettier for code formatting? … No
✔ Add Vue DevTools 7 extension for debugging? (experimental) … No

安裝需要的套件後,啟動專案

cd shopping_cart_frontend
bun i
bun dev

前往http://localhost:5173/,可以看到寫著You did it!的網頁,確認專案成功建立。

刪除asset、views資料夾,清空components、router、stores裡的文件。

導入Tailwind CSS

我們在React時做過同樣的事,Vue的流程也一樣

bun install -D tailwindcss postcss autoprefixer
bunx tailwindcss init -p

修改tailwind.config.js

/** @type {import('tailwindcss').Config} */
export default {
  content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
  theme: {
    extend: {}
  },
  plugins: []
}

新增src/style.css

@tailwind base;
@tailwind components;
@tailwind utilities;

在main.js啟用Tailwind CSS

import './style.css'

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
//import router from './router'

const app = createApp(App)

app.use(createPinia())
//app.use(router)

app.mount('#app')

修改App.vue

<script setup></script>

<template>
  <h1 class="text-3xl font-bold underline text-sky-500">Hello world!</h1>
</template>

啟動專案,可以看到藍色下劃線的Hello world!。

導航列

新增src/components/MainNavbar.vue,程式碼的取得來源是https://tailwindui.com/components/application-ui/navigation/navbars。


我們針對專案使用到的部分做些修改,修改圖示和選單內容,和按下購物車時會前往/cart。

<template>
	<Disclosure as="nav" class="bg-gray-800" v-slot="{ open }">
		<div class="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
			<div class="relative flex h-16 items-center justify-between">
				<div class="absolute inset-y-0 left-0 flex items-center sm:hidden">
					<!-- Mobile menu button-->
					<DisclosureButton
						class="relative inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
					>
						<span class="absolute -inset-0.5" />
						<span class="sr-only">Open main menu</span>
						<Bars3Icon v-if="!open" class="block h-6 w-6" aria-hidden="true" />
						<XMarkIcon v-else class="block h-6 w-6" aria-hidden="true" />
					</DisclosureButton>
				</div>
				<div
					class="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start"
				>
					<div class="hidden sm:ml-6 sm:block">
						<div class="flex space-x-4">
							<a
								v-for="item in navigation"
								:key="item.name"
								:href="item.href"
								:class="[
									item.current
										? 'bg-gray-900 text-white'
										: 'text-gray-300 hover:bg-gray-700 hover:text-white',
									'rounded-md px-3 py-2 text-sm font-medium',
								]"
								:aria-current="item.current ? 'page' : undefined"
								>{{ item.name }}</a
							>
						</div>
					</div>
				</div>
				<div
					class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0"
				>
					<button
						type="button"
						class="relative rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
                        @click="goToCart"
                        >
						<span class="absolute -inset-1.5" />
						<span class="sr-only">Shopping Cart</span>
                        
                            <ShoppingCartIcon class="h-6 w-6" aria-hidden="true"></ShoppingCartIcon>
						
                    
                       
					</button>

					<!-- Profile dropdown -->
					<Menu as="div" class="relative ml-3">
						<div>
							<MenuButton
								class="relative flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
							>
								<span class="absolute -inset-1.5" />
								<span class="sr-only">Open user menu</span>
								<UserIcon class="h-8 w-8 rounded-full bg-white"> </UserIcon>
							</MenuButton>
						</div>
						<transition
							enter-active-class="transition ease-out duration-100"
							enter-from-class="transform opacity-0 scale-95"
							enter-to-class="transform opacity-100 scale-100"
							leave-active-class="transition ease-in duration-75"
							leave-from-class="transform opacity-100 scale-100"
							leave-to-class="transform opacity-0 scale-95"
						>
							<MenuItems
								class="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
							>
								<MenuItem v-slot="{ active }">
									<a
										href="/login"
										:class="[
											active ? 'bg-gray-100' : '',
											'block px-4 py-2 text-sm text-gray-700',
										]"
										>Login</a
									>
								</MenuItem>
								<MenuItem v-slot="{ active }">
									<a
										href="/signup"
										:class="[
											active ? 'bg-gray-100' : '',
											'block px-4 py-2 text-sm text-gray-700',
										]"
										>Sign up</a
									>
								</MenuItem>
								<MenuItem v-slot="{ active }">
									<a
										href="/signout"
										:class="[
											active ? 'bg-gray-100' : '',
											'block px-4 py-2 text-sm text-gray-700',
										]"
										>Logout</a
									>
								</MenuItem>
							</MenuItems>
						</transition>
					</Menu>
				</div>
			</div>
		</div>

		<DisclosurePanel class="sm:hidden">
			<div class="space-y-1 px-2 pb-3 pt-2">
				<DisclosureButton
					v-for="item in navigation"
					:key="item.name"
					as="a"
					:href="item.href"
					:class="[
						item.current
							? 'bg-gray-900 text-white'
							: 'text-gray-300 hover:bg-gray-700 hover:text-white',
						'block rounded-md px-3 py-2 text-base font-medium',
					]"
					:aria-current="item.current ? 'page' : undefined"
					>{{ item.name }}</DisclosureButton
				>
			</div>
		</DisclosurePanel>
	</Disclosure>
</template>

<script setup>
import {
	Disclosure,
	DisclosureButton,
	DisclosurePanel,
	Menu,
	MenuButton,
	MenuItem,
	MenuItems,
} from "@headlessui/vue";
import {
	Bars3Icon,
	ShoppingCartIcon,
	UserIcon,
	XMarkIcon,
} from "@heroicons/vue/24/outline";

const navigation = [{ name: "Add Product", href: "/add", current: true }];
const goToCart = () => {
    window.location.href = "/cart";
}

</script>

@click在按下時,會執行後面對應的script。


安裝使用到的套件

bun i @headlessui/vue @heroicons/vue

在App.vue中顯示MainNavbar

<script setup>
import MainNavbar from "./components/MainNavbar.vue";
</script>

<template>
  <MainNavbar />
</template>

確認按下後URL的變化。

  • Add Product→http://localhost:5173/add
  • 購物車圖示→http://localhost:5173/cart

按下用戶頭像後,繼續按下出現的選項

  • Login→http://localhost:5173/login
  • Sign up→http://localhost:5173/signup
  • Logout→http://localhost:5173/signout

登入頁面與Vue Router設定

上一段我們按下許多的按鈕,雖然URL有變化,但是頁面的內容都是相同的。

接下來,導入Vue Router進行路由的設定,根據URL顯示不同的結果。


修改main.js啟用Vue Router

import './style.css'

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'

const app = createApp(App)

app.use(createPinia())
app.use(router)

app.mount('#app')

建立src/components/RegisterForm.vue

<template>
	RegisterForm
</template>

在router/index.js增加註冊頁面的路由。

import RegisterForm from "@/components/RegisterForm.vue"
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/signup',
      name: 'signup',
      component: RegisterForm
    }
  ]
})

export default router

App.vue添加,根據Vue Router的設定改變顯示的內容。

<script setup>
import MainNavbar from "./components/MainNavbar.vue";
</script>

<template>
  <MainNavbar />
  <RouterView />
</template>

我們在專案點擊Sign up,在導航列的下方可以看到RegisterForm的文字,代表Vue Router的設定沒有錯誤。

修改MainNavbar.vue,導入Vue Router,把切換頁面的href換成@click,按下時執行router.push

<template>
	<Disclosure as="nav" class="bg-gray-800" v-slot="{ open }">
		<div class="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
			<div class="relative flex h-16 items-center justify-between">
				<div class="absolute inset-y-0 left-0 flex items-center sm:hidden">
					<!-- Mobile menu button-->
					<DisclosureButton
						class="relative inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
					>
						<span class="absolute -inset-0.5" />
						<span class="sr-only">Open main menu</span>
						<Bars3Icon v-if="!open" class="block h-6 w-6" aria-hidden="true" />
						<XMarkIcon v-else class="block h-6 w-6" aria-hidden="true" />
					</DisclosureButton>
				</div>
				<div
					class="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start"
				>
					<div class="hidden sm:ml-6 sm:block">
						<div class="flex space-x-4">
							<a
								v-for="item in navigation"
								:key="item.name"
								@click="router.push(item.href)"
								:class="[
									item.current
										? 'bg-gray-900 text-white'
										: 'text-gray-300 hover:bg-gray-700 hover:text-white',
									'rounded-md px-3 py-2 text-sm font-medium',
								]"
								:aria-current="item.current ? 'page' : undefined"
								>{{ item.name }}</a
							>
						</div>
					</div>
				</div>
				<div
					class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0"
				>
					<button
						type="button"
						class="relative rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
                        @click="router.push('/cart')"
                        >
						<span class="absolute -inset-1.5" />
						<span class="sr-only">Shopping Cart</span>
                        
                            <ShoppingCartIcon class="h-6 w-6" aria-hidden="true"></ShoppingCartIcon>
						
                    
                       
					</button>

					<!-- Profile dropdown -->
					<Menu as="div" class="relative ml-3">
						<div>
							<MenuButton
								class="relative flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
							>
								<span class="absolute -inset-1.5" />
								<span class="sr-only">Open user menu</span>
								<UserIcon class="h-8 w-8 rounded-full bg-white"> </UserIcon>
							</MenuButton>
						</div>
						<transition
							enter-active-class="transition ease-out duration-100"
							enter-from-class="transform opacity-0 scale-95"
							enter-to-class="transform opacity-100 scale-100"
							leave-active-class="transition ease-in duration-75"
							leave-from-class="transform opacity-100 scale-100"
							leave-to-class="transform opacity-0 scale-95"
						>
							<MenuItems
								class="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
							>
								<MenuItem v-slot="{ active }">
									<a
									@click="router.push('/login')"	
									
										:class="[
											active ? 'bg-gray-100' : '',
											'block px-4 py-2 text-sm text-gray-700',
										]"
										>Login</a
									>
								</MenuItem>
								<MenuItem v-slot="{ active }">
									<a
									@click="router.push('/signup')"
										:class="[
											active ? 'bg-gray-100' : '',
											'block px-4 py-2 text-sm text-gray-700',
										]"
										>Sign up</a
									>
								</MenuItem>
								<MenuItem v-slot="{ active }">
									<a
										:class="[
											active ? 'bg-gray-100' : '',
											'block px-4 py-2 text-sm text-gray-700',
										]"
										@click="logout"
										>Logout</a
									>
								</MenuItem>
							</MenuItems>
						</transition>
					</Menu>
				</div>
			</div>
		</div>

		<DisclosurePanel class="sm:hidden">
			<div class="space-y-1 px-2 pb-3 pt-2">
				<DisclosureButton
					v-for="item in navigation"
					:key="item.name"
					@click="router.push(item.href)"
					:class="[
						item.current
							? 'bg-gray-900 text-white'
							: 'text-gray-300 hover:bg-gray-700 hover:text-white',
						'block rounded-md px-3 py-2 text-base font-medium',
					]"
					:aria-current="item.current ? 'page' : undefined"
					>{{ item.name }}</DisclosureButton
				>
			</div>
		</DisclosurePanel>
	</Disclosure>
</template>

<script setup>
import {
	Disclosure,
	DisclosureButton,
	DisclosurePanel,
	Menu,
	MenuButton,
	MenuItem,
	MenuItems,
} from "@headlessui/vue";
import {
	Bars3Icon,
	ShoppingCartIcon,
	UserIcon,
	XMarkIcon,
} from "@heroicons/vue/24/outline";
import router from "@/router";

const navigation = [{ name: "Home", href: "/", current: false },{ name: "Add Product", href: "/add", current: false }];

const logout = () => {
  router.push("/");
};

</script>


繼續寫RegisterForm.vue的程式碼

<template>
	<div className="flex justify-center min-h-screen items-center bg-gray-100">
		<form
			method="post"
			role="form"
			className="bg-white p-6 rounded-lg shadow-md w-full max-w-md"
			@submit.prevent="handleSubmit"
		>
			<div className="mb-4">
				<label className="text-gray-700 font-bold mb-2"> Email </label>
				<input
					placeholder="Enter email address"
					type="text"
					className="shadow border rounded w-full py-2 px-3 text-gray-700"
					name="email"
					autocomplete="current-email"
					v-model="email"
				/>
			</div>

			<div className="mb-4">
				<label className="text-gray-700 font-bold mb-2"> Password </label>
				<input
					placeholder="Enter password"
					type="password"
					className="shadow border rounded w-full py-2 px-3 text-gray-700"
					name="password"
					autocomplete="current-password"
					v-model="password"
				/>
			</div>

			<button
				type="submit"
				className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
			>
				Register
			</button>
		</form>
	</div>
</template>

<script>
import { ref } from 'vue';
import { useRouter } from 'vue-router';

export default {
  setup() {
    const router = useRouter();
    //取得表單的email和password
    const email = ref('');
    const password = ref('');

		//處理按下表單送出後的事件
    const handleSubmit = () => {
		    //顯示email以及password
        console.log(email.value);
        console.log(password.value);
        router.push('/');
    };

    return {
      email,
      password,
      handleSubmit,
    };
  },
};
</script>

@submit代表按下送出後會執行的內容,後面加上的.prevent,是為了阻止HTML的方式送出表單,讓Vue處理表單。

v-model會將這個欄位的內容綁定到對應名稱的const。


在導入後端前,我們先測試一下表單送出的功能。

填寫email和password欄位,按下F12切換到Console,點擊Register送出。

就能在Console中看到剛填入的email、password數值。

導入Spring Boot後端

在SecurityConfig.java取消註解CORS的部分

.cors(cors -> cors.configurationSource(new CorsConfigurationSource() {
                    @Override
                    public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
                        CorsConfiguration config = new CorsConfiguration();
                        config.setAllowedOrigins(Arrays.asList(
                                "http://localhost:5173"
                        ));
                        config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
                        config.setAllowCredentials(true);
                        config.setExposedHeaders(Arrays.asList("Authorization"));
                        config.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
                        config.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
                        config.setMaxAge(3600L);
                        return config;
                    }
                }))

這樣Vue才能和後端溝通。


安裝axios

bun i axios

我們在RegisterForm.vue修改handleSubmit,讓它傳送註冊請求

const handleSubmit = async () => {
      try {
		//傳送註冊請求
        const response = await axios.post('http://localhost:8080/auth/signup', {
          email: email.value,
          password: password.value,
        });
		//顯示註冊成功訊息
        console.log(response.data);
        router.push('/');
      } catch (error) {
	      alert("Email already exists");
        console.log(error);
      }
    };

啟動後端,再次進行註冊,成功的話可以在Console看到以下訊息

{jwt: 'eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE3MjMyNjk0NTYsImV4c…9tIn0.wxhXF6khkLpLwI2iKzQ4SLq5nyKNWn9SufnZ8zvJYkI'
, message: 'Signup Success'}

導入Pinia區分登入和未登入

註冊成功後,應該會登入,但因為我們沒有做相關的處理,所以現在我們使用Pinia來管理登入的狀態。

修改main.js,啟用Pinia

import './style.css'

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.use(router)
app.mount('#app')

新增src/stores/auth.js,用來管理token的狀態。

import { defineStore } from 'pinia';

export const useAuthStore = defineStore('auth', {
  state: () => ({
    token: null, //token的初始值為null
  }),
  getters: {
    isAuthenticated: (state) => !!state.token, //如果token不是null就回傳true,代表已登入
  },
  actions: {
    setToken(token) {
      this.token = token; //將token設定為傳入的token
    },
    clearToken() {
      this.token = null; //將token清空
    },
  },
});

對MainNavbar.vue做修改,在未登入時顯示Login、Sign up,其餘的Home、Add Product、購物車按鈕都隱藏。

登入後,顯示Home、Add Product、購物車,按下用戶頭像後,改為顯示Logout

<template>
	<Disclosure as="nav" class="bg-gray-800" v-slot="{ open }">
		<div class="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
			<div class="relative flex h-16 items-center justify-between">
				<div class="absolute inset-y-0 left-0 flex items-center sm:hidden">
					<!-- Mobile menu button-->
					<DisclosureButton
						class="relative inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
					>
						<span class="absolute -inset-0.5" />
						<span class="sr-only">Open main menu</span>
						<Bars3Icon v-if="!open" class="block h-6 w-6" aria-hidden="true" />
						<XMarkIcon v-else class="block h-6 w-6" aria-hidden="true" />
					</DisclosureButton>
				</div>
				<div
					class="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start"
				>
					<div class="hidden sm:ml-6 sm:block">
						<div class="flex space-x-4" v-if="isAuthenticated">
							<a
								v-for="item in navigation"
								:key="item.name"
								@click="router.push(item.href)"
								:class="[
									item.current
										? 'bg-gray-900 text-white'
										: 'text-gray-300 hover:bg-gray-700 hover:text-white',
									'rounded-md px-3 py-2 text-sm font-medium',
								]"
								:aria-current="item.current ? 'page' : undefined"
								>{{ item.name }}</a
							>
						</div>
					</div>
				</div>
				<div
					class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0"
				>
					<button
						v-if="isAuthenticated"
						type="button"
						class="relative rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
                        @click="router.push('/cart')"
                        >
						<span class="absolute -inset-1.5" />
						<span class="sr-only">Shopping Cart</span>
                        
                            <ShoppingCartIcon class="h-6 w-6" aria-hidden="true"></ShoppingCartIcon>
						
                    
                       
					</button>

					<!-- Profile dropdown -->
					<Menu as="div" class="relative ml-3">
						<div>
							<MenuButton
								class="relative flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
							>
								<span class="absolute -inset-1.5" />
								<span class="sr-only">Open user menu</span>
								<UserIcon class="h-8 w-8 rounded-full bg-white"> </UserIcon>
							</MenuButton>
						</div>
						<transition
							enter-active-class="transition ease-out duration-100"
							enter-from-class="transform opacity-0 scale-95"
							enter-to-class="transform opacity-100 scale-100"
							leave-active-class="transition ease-in duration-75"
							leave-from-class="transform opacity-100 scale-100"
							leave-to-class="transform opacity-0 scale-95"
						>
							<MenuItems
								class="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
							>
								<MenuItem v-slot="{ active }" v-if="!isAuthenticated">
									<a
									@click="router.push('/login')"	
									
										:class="[
											active ? 'bg-gray-100' : '',
											'block px-4 py-2 text-sm text-gray-700',
										]"
										>Login</a
									>
								</MenuItem>
								<MenuItem v-slot="{ active }" v-if="!isAuthenticated">
									<a
									@click="router.push('/signup')"
										:class="[
											active ? 'bg-gray-100' : '',
											'block px-4 py-2 text-sm text-gray-700',
										]"
										>Sign up</a
									>
								</MenuItem>
								<MenuItem v-slot="{ active }" v-if="isAuthenticated">
									<a
										:class="[
											active ? 'bg-gray-100' : '',
											'block px-4 py-2 text-sm text-gray-700',
										]"
										@click="logout"
										>Logout</a
									>
								</MenuItem>
							</MenuItems>
						</transition>
					</Menu>
				</div>
			</div>
		</div>

		<DisclosurePanel class="sm:hidden">
			<div class="space-y-1 px-2 pb-3 pt-2" v-if="isAuthenticated">
				<DisclosureButton
					v-for="item in navigation"
					:key="item.name"
					@click="router.push(item.href)"
					:class="[
						item.current
							? 'bg-gray-900 text-white'
							: 'text-gray-300 hover:bg-gray-700 hover:text-white',
						'block rounded-md px-3 py-2 text-base font-medium',
					]"
					:aria-current="item.current ? 'page' : undefined"
					>{{ item.name }}</DisclosureButton
				>
			</div>
		</DisclosurePanel>
	</Disclosure>
</template>

當v-if的結果是true會顯示,反之false則隱藏。

在MainNavbar.vue的script的部分添加

import { useAuthStore } from '@/stores/auth';
import { computed } from 'vue';

const authStore = useAuthStore();

const logout = () => {
  authStore.clearToken();//使用clearToken清除儲存的token
  window.location.href = "/";
};

const isAuthenticated = computed(() => authStore.isAuthenticated);//使用computed來判斷是否有token

現在觀察前端網頁,點用戶頭像會只剩Login和Sign up,Logout消失了。

不過我們還沒有把登入狀態放到auth.js中,所以只會顯示Login、Sign up。


我們在RegisterForm.vue,導入auth.js管理token狀態,在註冊後登入系統。

import { useAuthStore } from '@/stores/auth';

export default {
  setup() {
    // ...
	//使用pinia
    const authStore = useAuthStore();
    //...
    
    const handleSubmit = async () => {
      try {
		//...
		//設定token
        authStore.setToken(response.data.jwt);
        router.push('/');
      } catch (error) {
        //...
      }
    };
    //...
   },
  };

註冊完成後,點擊用戶頭像,因為我們有登入,所以只會顯示Logout。

只要按下Logout,就能回到未登入的狀態。

登入頁面

複製RegisterForm.vue改名為LoginForm.vue

  • 把按鈕的Register改成Login
  • 把axios.post的URL改成http://localhost:8080/auth/login
  • 把alert的內容修改為Invalid email or password

在router/index.js添加LoginForm的路由

routes: [
    {
      path: '/signup',
      name: 'signup',
      component: RegisterForm
    },
    {
      path: '/login',
      name: 'login',
      component: LoginForm
    }
  ]

在前端測試登入頁面,只要email和密碼正確就能登入。


上一篇
Day26 前端專案:React(2) 待辦事項清單的整合與實作
下一篇
Day28 前端專案:Vue.js(2)持久化保存與商品頁面
系列文
我的SpringBoot絕學:7+2個專案,從新手變專家31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言